﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Cryptography;
using System.Text;
using System.Reflection;

namespace BankCreditPortfolio.DAL
{
    public static class PasswordHelper
    {
        #region Fields

        private const int SaltLength = 32;

        private static readonly Dictionary<PasswordMode, HashAlgorithm> hashAlgorithmMapping = new Dictionary<PasswordMode, HashAlgorithm>
        {
            { PasswordMode.MD5, new MD5CryptoServiceProvider() },
            { PasswordMode.SHA1, new SHA1Managed() },
            { PasswordMode.SHA256, new SHA256Managed() },
            { PasswordMode.SHA384, new SHA384Managed() },
            { PasswordMode.SHA512, new SHA512Managed() },
        };

        #endregion

        #region Methods

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// base64-encoded result. Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">Plaintext value to be hashed. The function does not check whether this parameter is null.</param>
        /// <param name="mode">Password encryption mode.</param>
        /// <returns>
        /// Password hash and salt.
        /// </returns>
        public static PasswordHash Encrypt(String plainText, PasswordMode mode)
        {
            PasswordHash result;

            if (hashAlgorithmMapping.ContainsKey(mode))
            {
                HashAlgorithm algorithm = hashAlgorithmMapping[mode];

                var saltBytes = new byte[SaltLength];
                RandomNumberGenerator.Create().GetBytes(saltBytes);
                var passwordBytes = Encoding.UTF8.GetBytes(plainText);
                var hashBytes = Encrypt(passwordBytes, saltBytes, algorithm);

                result = new PasswordHash
                {
                    Salt = Convert.ToBase64String(saltBytes),
                    Hash = Convert.ToBase64String(hashBytes)
                };
            }
            else
            {
                result = new PasswordHash
                {
                    Salt = String.Empty,
                    Hash = plainText
                };
            }

            return result;
        }

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// base64-encoded result. Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">Plaintext value to be hashed. The function does not check whether this parameter is null.</param>
        /// <param name="mode">Password encryption mode.</param>
        /// <param name="salt">Random bits that are used as one of the inputs to a key derivation function (formatted as a base64-encoded string).</param>
        /// <returns>Password hash and salt.</returns>
        public static PasswordHash Encrypt(String plainText, PasswordMode mode, String salt)
        {
            PasswordHash result;

            if (hashAlgorithmMapping.ContainsKey(mode))
            {
                HashAlgorithm algorithm = hashAlgorithmMapping[mode];

                var saltBytes = Convert.FromBase64String(salt);
                var passwordBytes = Encoding.UTF8.GetBytes(plainText);
                var hashBytes = Encrypt(passwordBytes, saltBytes, algorithm);

                result = new PasswordHash
                {
                    Salt = salt,
                    Hash = Convert.ToBase64String(hashBytes)
                };
            }
            else
            {
                result = new PasswordHash
                {
                    Salt = String.Empty,
                    Hash = plainText
                };
            }

            return result;
        }

        /// <summary>
        /// Compares a hash of the specified plain text value to a given hash
        /// value. Plain text is hashed with the same salt value as the original
        /// hash.
        /// </summary>
        /// <param name="plainText">Plain text to be verified against the specified hash. The function does not check whether this parameter is null.</param>
        /// <param name="expected">Password expected hash.</param>
        /// <param name="mode">Password encryption mode.</param>
        /// <returns>
        ///     <c>true</c> if computed hash mathes the specified hash the function; otherwise <c>false</c>.
        /// </returns>
        public static bool Verify(String plainText, PasswordHash expected, PasswordMode mode)
        {
            var hash = Encrypt(plainText, mode, expected.Salt);
            return expected.Equals(hash);
        }

        private static byte[] Encrypt(byte[] passwordBytes, byte[] saltBytes, HashAlgorithm algorithm)
        {
            var buffer = new byte[passwordBytes.Length + saltBytes.Length];
            passwordBytes.CopyTo(buffer, 0);
            saltBytes.CopyTo(buffer, passwordBytes.Length);
            return algorithm.ComputeHash(buffer);
        }

        #endregion
    }

   public enum PasswordMode
    {
        /// <summary>
        /// Password is not encrypted.
        /// </summary>
        ClearText,

        /// <summary>
        /// Password is encrypted as MD5 hash.
        /// </summary>
        MD5,

        /// <summary>
        /// Password is encrypted as SHA1 hash.
        /// </summary>
        SHA1,

        /// <summary>
        /// Password is encrypted as SHA256 hash.
        /// </summary>
        SHA256,

        /// <summary>
        /// Password is encrypted as SHA384 hash.
        /// </summary>
        SHA384,

        /// <summary>
        /// Password is encrypted as SHA512 hash.
        /// </summary>
        SHA512
    }

   public class PasswordHash
   {
       #region Properties

       /// <summary>
       /// Gets or sets the hash.
       /// </summary>
       /// <value>Hash value formatted as a base64-encoded string.</value>
       public String Hash { get; set; }

       /// <summary>
       /// Gets or sets the salt. Salt consists of random bits that are used as one of the inputs to a key derivation function.
       /// </summary>
       /// <value>Random bits formatted as a base64-encoded string.</value>
       public String Salt { get; set; }

       #endregion

       #region Equality methods

       /// <summary>
       /// Determines whether the specified <see cref="PasswordHash"/> is equal to this instance.
       /// </summary>
       /// <param name="other">The <see cref="PasswordHash"/> to compare with this instance.</param>
       /// <returns>
       ///     <c>true</c> if the specified <see cref="PasswordHash"/> is equal to this instance; otherwise, <c>false</c>.
       /// </returns>
       public bool Equals(PasswordHash other)
       {
           if (ReferenceEquals(null, other))
           {
               return false;
           }

           if (ReferenceEquals(this, other))
           {
               return true;
           }

           return Equals(other.Hash, Hash) && Equals(other.Salt, Salt);
       }

       /// <summary>
       /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
       /// </summary>
       /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
       /// <returns>
       ///     <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
       /// </returns>
       public override bool Equals(object obj)
       {
           if (ReferenceEquals(null, obj))
           {
               return false;
           }

           if (ReferenceEquals(this, obj))
           {
               return true;
           }

           if (obj.GetType() != typeof(PasswordHash))
           {
               return false;
           }

           return Equals((PasswordHash)obj);
       }

       /// <summary>
       /// Returns a hash code for this instance.
       /// </summary>
       /// <returns>
       /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
       /// </returns>
       public override int GetHashCode()
       {
           unchecked
           {
               int hashCode = 0;
               if (Hash != null)
               {
                   hashCode = hashCode * BaseObject.HashMultiplier ^ Hash.GetHashCode();
               }
               if (Salt != null)
               {
                   hashCode = hashCode * BaseObject.HashMultiplier ^ Salt.GetHashCode();
               }
               return hashCode;
           }
       }

       #endregion
   }

   public abstract class BaseObject
   {
       #region Fields

       /// <summary>
       /// To help ensure hashcode uniqueness, a carefully selected random number multiplier is used within the calculation. 
       /// Goodrich and Tamassia's Data Structures and Algorithms in Java asserts that 31, 33, 37, 39 and 41 will produce the fewest number of collissions.
       /// </summary>
       /// <remarks>
       /// See http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/ for more information.
       /// </remarks>
       public const int HashMultiplier = 31;

       /// <summary>
       /// This static member caches the domain signature properties to avoid looking them up for each instance of the same type.
       /// </summary>
       [ThreadStatic]
       private static Dictionary<Type, IEnumerable<PropertyInfo>> signaturePropertiesDictionary;

       #endregion

       #region Comparison support

       /// <summary>
       /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
       /// </summary>
       /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
       /// <returns>
       ///     <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
       /// </returns>
       public override bool Equals(Object obj)
       {
           BaseObject compareTo = obj as BaseObject;

           if (ReferenceEquals(this, compareTo))
           {
               return true;
           }

           return compareTo != null && GetInnerType().Equals(compareTo.GetInnerType()) && HasSameObjectSignatureAs(compareTo);
       }

       /// <summary>
       /// Returns a hash code for this instance.
       /// </summary>
       /// <remarks>
       /// This is used to provide the hashcode identifier of an object using the signature 
       /// properties of the object; although it's necessary for NHibernate's use, this can 
       /// also be useful for business logic purposes and has been included in this base 
       /// class, accordingly.  Since it is recommended that GetHashCode change infrequently, 
       /// if at all, in an object's lifetime, it's important that properties are carefully
       /// selected which truly represent the signature of an object.
       /// </remarks>
       /// <returns>
       /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
       /// </returns>
       public override int GetHashCode()
       {
           unchecked
           {
               int hashCode;

               IEnumerable<PropertyInfo> signatureProperties = GetSignatureProperties();

               if (signatureProperties.Any())
               {
                   // It's possible for two objects to return the same hash code based on 
                   // identically valued properties, even if they're of two different types, 
                   // so we include the object's type in the hash calculation
                   hashCode = GetType().GetHashCode();

                   foreach (PropertyInfo property in signatureProperties)
                   {
                       Object value = property.GetValue(this, null);

                       if (value != null)
                       {
                           hashCode = (hashCode * HashMultiplier) ^ value.GetHashCode();
                       }
                   }
               }
               else
               {
                   // If no properties were flagged as being part of the signature of the object,
                   // then simply return the hashcode of the base object as the hashcode.
                   hashCode = base.GetHashCode();
               }

               return hashCode;
           }
       }

       /// <summary>
       /// You may override this method to provide your own comparison routine.
       /// </summary>
       /// <param name="compareTo">The <see cref="BaseObject"/> to compare with this instance.</param>
       /// <returns>
       ///     <c>true</c> if the specified <see cref="BaseObject"/> has the same signature properties values as this instance; otherwise, <c>false</c>.
       /// </returns>
       public virtual bool HasSameObjectSignatureAs(BaseObject compareTo)
       {
           IEnumerable<PropertyInfo> signatureProperties = GetSignatureProperties();

           foreach (PropertyInfo property in signatureProperties)
           {
               Object valueOfThisObject = property.GetValue(this, null);
               Object valueToCompareTo = property.GetValue(compareTo, null);

               if (valueOfThisObject == null && valueToCompareTo == null)
               {
                   continue;
               }

               if ((valueOfThisObject == null ^ valueToCompareTo == null) || (!valueOfThisObject.Equals(valueToCompareTo)))
               {
                   return false;
               }
           }

           // If we've gotten this far and signature properties were found, then we can
           // assume that everything matched; otherwise, if there were no signature 
           // properties, then simply return the default bahavior of Equals
           return signatureProperties.Any() || base.Equals(compareTo);
       }

       /// <summary>
       /// <para>Gets the signature properties.</para>
       /// </summary>
       /// <remarks>
       /// Static dictionary used to reduce performance. So properties will be retrieved 
       /// only once per class for each thread after application start.
       /// </remarks>
       /// <returns>Set of properties that should be included in the object signature comparison.</returns>
       public virtual IEnumerable<PropertyInfo> GetSignatureProperties()
       {
           IEnumerable<PropertyInfo> properties;

           // Init the signaturePropertiesDictionary here due to reasons described at 
           // http://blogs.msdn.com/jfoscoding/archive/2006/07/18/670497.aspx
           if (signaturePropertiesDictionary == null)
           {
               signaturePropertiesDictionary = new Dictionary<Type, IEnumerable<PropertyInfo>>();
           }

           // TryGetValue method used to reduce performance instead of 2 method calls (ContainsKey, GetValue).
           if (!signaturePropertiesDictionary.TryGetValue(GetType(), out properties))
           {
               properties = GetTypeSpecificSignatureProperties();
               signaturePropertiesDictionary[GetType()] = properties;
           }

           return properties;
       }

       /// <summary>
       /// Enforces the template method pattern to have child objects determine which specific
       /// properties should and should not be included in the object signature comparison.  
       /// </summary>
       /// <remarks>
       /// BaseObject already takes care of performance caching, so this method
       /// shouldn't worry about caching, just return set of properties.
       /// </remarks>
       /// <returns>Set of properties that should be included in the object signature comparison.</returns>
       protected abstract IEnumerable<PropertyInfo> GetTypeSpecificSignatureProperties();

       /// <summary>
       /// When NHibernate proxies objects, it masks the type of the actual entity object.
       /// This wrapper burrows into the proxied object to get its actual type.
       /// Although this assumes NHibernate is being used, it doesn't require any NHibernate
       /// related dependencies and has no bad side effects if NHibernate isn't being used.
       /// </summary>
       /// <remarks>Related discussion is at http://groups.google.com/group/sharp-architecture/browse_thread/thread/ddd05f9baede023a .</remarks>
       /// <returns>Type of inner object for proxied objects and type for simple objects.</returns>
       protected virtual Type GetInnerType()
       {
           return GetType();
       }

       #endregion
   }
}